﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Exceptions;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Ports
{
    /// <summary>
    /// 串口通信客户端
    /// </summary>
    public class SerialPortClient : ClientAbs
    {
        private readonly static ITimestampFactory _sendDataIntervalTimestampFactory;
        static SerialPortClient()
        {
            _sendDataIntervalTimestampFactory = new TicksTimestampFactory(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local), TimestampUnit.Nanosecond100);
        }



        private readonly SerialPortClientInitPara _serialPortInfo;
        /// <summary>
        /// 获取串口信息
        /// </summary>
        public SerialPortClientInitPara SerialPortInfo
        {
            get { return this._serialPortInfo; }
        }


        private ISerialPort _serialPort = null;

        /// <summary>
        /// 表示将处理 System.IO.Ports.SerialPort 对象的串行管脚更改事件的方法
        /// </summary>
        [MonitoringDescription("SerialPinChanged")]
        public event EventHandler<EventArgs<SerialPortPinChange>> PinChanged;

        /// <summary>
        /// 连接改变事件
        /// </summary>
        [MonitoringDescription("连接改变事件")]
        public event EventHandler<SerialPortConnectionChangedArgs> ConnectChanged;
        /// <summary>
        /// 触发连接改变事件
        /// </summary>
        /// <param name="status">连接状态</param>
        private void OnRaiseConnectChanged(ConnectionStatus status)
        {
            var handler = this.ConnectChanged;
            if (handler != null)
            {
                handler(this, new SerialPortConnectionChangedArgs(status, this._serialPortInfo.PortName));
            }
        }

        /// <summary>
        /// 当前串口是否已打开(已连接)
        /// </summary>
        public bool Connected
        {
            get
            {
                ISerialPort serialPort = this._serialPort;
                if (serialPort == null)
                {
                    return false;
                }

                return serialPort.IsOpen;
            }
        }

        /// <summary>
        /// 异步打开串口线程
        /// </summary>
        private ThreadEx _asynOpenThread = null;

        /// <summary>
        /// 该是否已释放[true:已释放;false:未已释放]
        /// </summary>
        private bool _disposabled = false;


        private readonly SerialPortUnpackerPara _unpackerPara;
        private byte[] _buffer = null;
        private int _dataLength = 0;
        private int _bufferReadOffset = 0;
        private int _bufferWirteOffset = 0;



        private readonly object _sendIntervalLock = new object();
        private readonly long _sendInterval100NS = 0;
        private long _lastSendDataTimestamp100NS = 0;
        private readonly CancellationTokenEx _sendDataIntervalToken = null;


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="initPara">串口信息</param>
        public SerialPortClient(SerialPortClientInitPara initPara)
            : base(initPara)
        {
            if (initPara == null)
            {
                throw new ArgumentNullException(nameof(initPara));
            }

            this._serialPortInfo = initPara;
            this._buffer = new byte[initPara.ReceiveBufferSize];

            var unpackerPara = new SerialPortUnpackerPara();
            unpackerPara.Client = this;
            unpackerPara.LinkInfo = new SerialPortLinkInfo(initPara.PortName);
            this._unpackerPara = unpackerPara;

            this._sendInterval100NS = initPara.SendInterval100NS;
            if (this._sendInterval100NS > 0)
            {
                this._sendDataIntervalToken = new CancellationTokenEx();
            }
        }



        /// <summary>
        /// 同步建立串口通信连接线程
        /// </summary>
        public void Open()
        {
            if (this._serialPort == null)
            {
                this.PrimitiveOpenSerialPort(this._serialPortInfo);
            }
            else if (!this._serialPort.IsOpen)
            {
                this._serialPort.Open();
                this.OnRaiseConnectChanged(ConnectionStatus.Opened);
            }
        }

        /// <summary>
        /// 异步建立串口通信连接线程
        /// </summary>
        public void AsynOpen()
        {
            if (this._asynOpenThread == null)
            {
                this._asynOpenThread = new ThreadEx(this.AsynOpenThreadMethod, "异步建立串口通信连接线程", true);
            }
            else if (this._asynOpenThread.IsRuning)
            {
                return;
            }

            this._asynOpenThread.Start();
        }

        private void PrimitiveOpenSerialPort(SerialPortClientInitPara serialPortInfo)
        {
            if (this._disposabled)
            {
                return;
            }

            ISerialPortFactory factory = SerialPortFactoryManager.Factory;
            if (factory == null)
            {
                throw new ApplicationException($"串口工厂管理类\"{nameof(SerialPortFactoryManager)}\"中需要先指定实现接口\"{nameof(ISerialPortFactory)}\"的串口工厂实例");
            }

            string[] portNames = factory.GetPortNames();
            if (portNames.Length == 0)
            {
                throw new ApplicationException("没有可用的串口");
            }

            if (!portNames.Contains(serialPortInfo.PortName))
            {
                throw new ArgumentException($"名为\"{serialPortInfo.PortName}\"的串口不可用,可用的串口包括:\"{string.Join(",", portNames)}\"");
            }

            try
            {
                var serialPort = factory.CreateSerialPort(serialPortInfo);
                serialPort.DataReceived += SerialPort_DataReceived;
                serialPort.ErrorReceived += SerialPort_ErrorReceived;
                serialPort.PinChanged += SerialPort_PinChanged;
                serialPort.Open();
                this._bufferReadOffset = 0;
                this._bufferWirteOffset = 0;
                this._dataLength = 0;
                this._serialPort = serialPort;
            }
            catch (UnauthorizedAccessException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new ApplicationException("创建并打开指定的串口异常", ex);
            }

            this.OnRaiseConnectChanged(ConnectionStatus.Opened);
        }




        private void SerialPort_PinChanged(object sender, EventArgs<SerialPortPinChange> e)
        {
            try
            {
                this.PinChanged?.Invoke(sender, e);
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "处理串口PinChanged事件时发生异常");
            }
        }

        private void SerialPort_ErrorReceived(object sender, EventArgs<SerialPortError> e)
        {
            try
            {
                this.ReleaSeserialPort();
                this.OnRaiseConnectChanged(ConnectionStatus.Closed);
                this.AsynOpen();
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "处理串口接收数据错误时发生异常");
            }
        }

        private void SerialPort_DataReceived(object sender, EventArgs<SerialPortData> e)
        {
            try
            {
                if (this._disposabled)
                {
                    return;
                }

                var serialPort = (ISerialPort)sender;
                int revLen = serialPort.BytesToRead;

                if (revLen > 0)
                {
                    int reqLen = this._dataLength + revLen;
                    if (this._buffer == null || this._buffer.Length < reqLen)
                    {
                        //扩容
                        int extendLen;
                        if (reqLen > 4096)
                        {
                            extendLen = (int)(reqLen * 1.5f);//大于了4k后,每次扩容为原来的1.5倍
                        }
                        else
                        {
                            extendLen = reqLen * 2;//小于4k时,每次扩容为原来的2倍
                        }

                        var buffer = new byte[extendLen];
                        if (this._buffer != null && this._dataLength > 0)
                        {
                            Array.Copy(this._buffer, this._bufferReadOffset, buffer, 0, this._dataLength);
                        }
                        this._buffer = buffer;
                    }
                    else
                    {
                        if (this._buffer.Length - this._bufferWirteOffset < revLen)
                        {
                            //buffer中剩余的空间不够,将数据移动到起始位置,腾出足够的空间接收数据
                            Array.Copy(this._buffer, 0, this._buffer, this._bufferReadOffset, this._dataLength);
                            this._bufferReadOffset = 0;
                            this._bufferWirteOffset = this._dataLength;
                        }
                    }

                    //从串口读取数据
                    int revLen2 = revLen;
                    while (revLen2 > 0)
                    {
                        var readByCount = serialPort.Read(this._buffer, this._bufferWirteOffset, revLen2);
                        this._dataLength += readByCount;
                        this._bufferWirteOffset += readByCount;
                        revLen2 -= readByCount;
                    }
                }

                this._unpackerPara.EventType = e.Para;
                this._unpackerPara.Offset = this._bufferReadOffset;
                this._unpackerPara.SerialPort = serialPort;
                this._unpackerPara.Buffer = this._buffer;
                this._unpackerPara.ReceiveLength = revLen;
                this._unpackerPara.DataLength = this._dataLength;
                int unpackgeLen = base._initPara.Unpacker.UnPackge(this._unpackerPara);
                if (unpackgeLen > 0)
                {
                    this._bufferReadOffset += unpackgeLen;
                    this._dataLength -= unpackgeLen;
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "读取串口接收到的数据时发生异常");
            }
        }


        private void AsynOpenThreadMethod(ThreadExPara threadPara)
        {
            try
            {
                this.ReleaSeserialPort();

                SerialPortClientInitPara serialPortInfo = this._serialPortInfo;
                int asynOpenIntervalMilliseconds = serialPortInfo.GetAsynOpenIntervalMilliseconds();

                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        this.PrimitiveOpenSerialPort(serialPortInfo);
                        break;
                    }
                    catch (Exception exi)
                    {
                        ZLoger.Error(exi, "创建并打开指定的串口异常");
                    }

                    if (threadPara.WaitOne(asynOpenIntervalMilliseconds))
                    {
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }



        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            if (this._serialPort != null)
            {
                if (this._serialPort.IsOpen)
                {
                    this._serialPort.Close();
                    this.ReleaSeserialPort();
                    this.OnRaiseConnectChanged(ConnectionStatus.Closed);
                }
            }
        }




        private ISerialPort GetSerialPort()
        {
            if (this._disposabled)
            {
                throw new ObjectDisposedException(this.ToString());
            }

            ISerialPort serialPort = this._serialPort;
            if (serialPort == null)
            {
                throw new ApplicationException("串口连接未建立");
            }

            return serialPort;
        }



        #region 发送数据

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sendDataOptions">发送的数据选项</param>
        protected override void PrimitiveSend(ISendDataOptions sendDataOptions)
        {
            if (sendDataOptions == null)
            {
                return;
            }

            var tcpSendOptions = (SerialPortSendDataOptions)sendDataOptions;
            BufferData bufferData = tcpSendOptions.Data;
            if (bufferData == null)
            {
                return;
            }

            this.Send(bufferData.Bytes, (int)bufferData.Offset, (int)bufferData.Length);
        }


        /// <summary>
        /// 将指定的字符串写入串行端口
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.ServiceProcess.TimeoutException:该操作未在超时时间到期之前完成。
        /// </summary>
        /// <param name="text">输出字符串</param>
        public void Send(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }


            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    this.GetSerialPort().Send(text);
                }
            }
            else
            {
                this.GetSerialPort().Send(text);
            }
        }

        /// <summary>
        /// 使用缓冲区的数据将指定数量的字节写入串行端口
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.ArgumentOutOfRangeException:offset 或 count 参数超出了所传递的 buffer 的有效区域。offset 或 count 小于零。
        /// T:System.ArgumentException:offset 加上 count 大于 buffer 的长度。
        /// T:System.ServiceProcess.TimeoutException:该操作未在超时时间到期之前完成。
        /// </summary>
        /// <param name="buffer">包含要写入端口的数据的字节数组</param>
        /// <param name="offset">buffer 参数中从零开始的字节偏移量，从此处开始将字节复制到端口</param>
        /// <param name="size">要写入的字节数</param>
        public void Send(byte[] buffer, int offset, int size)
        {
            if (buffer == null || buffer.Length == 0)
            {
                return;
            }

            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    this.GetSerialPort().Send(buffer, offset, size);
                }
            }
            else
            {
                this.GetSerialPort().Send(buffer, offset, size);
            }
        }

        /// <summary>
        /// 使用缓冲区的数据将指定数量的字符写入串行端口
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.ArgumentOutOfRangeException: offset 或 count 参数超出了所传递的 buffer 的有效区域。offset 或 count 小于零。
        /// T:System.ArgumentException:offset 加上 count 大于 buffer 的长度。
        /// T:System.ServiceProcess.TimeoutException:该操作未在超时时间到期之前完成。
        /// </summary>
        /// <param name="buffer">包含要写入端口的数据的字符数组</param>
        /// <param name="offset">buffer 参数中从零开始的字节偏移量，从此处开始将字节复制到端口</param>
        /// <param name="size">要写入的字符数</param>
        public void Send(char[] buffer, int offset, int size)
        {
            if (buffer == null || buffer.Length == 0)
            {
                return;
            }

            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    this.GetSerialPort().Send(buffer, offset, size);
                }
            }
            else
            {
                this.GetSerialPort().Send(buffer, offset, size);
            }
        }

        /// <summary>
        /// 将指定的字符串和 System.IO.Ports.SerialPort.NewLine 值写入输出缓冲区
        /// 异常:
        /// T:System.InvalidOperationException:指定的端口未打开。
        /// T:System.TimeoutException:System.IO.Ports.SerialPort.WriteLine(System.String) 方法未能写入流。
        /// </summary>
        /// <param name="text">写入输出缓冲区的字符串</param>
        public void SendLine(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    this.GetSerialPort().Send(text);
                }
            }
            else
            {
                this.GetSerialPort().Send(text);
            }
        }

        /// <summary>
        /// 等待发送间隔[返回true,有等待发送间隔;返回false无等待]
        /// </summary>
        /// <returns>返回true,有等待发送间隔;返回false无等待</returns>
        private bool WaitSendInterval()
        {
            var mod100NS = _sendDataIntervalTimestampFactory.GetTimestamp() - this._lastSendDataTimestamp100NS;
            if (mod100NS < this._sendInterval100NS)
            {
                //两次之间的发送间隔小于了最小值，则等待差值毫秒
                var ts = TimeSpan.FromMilliseconds(((double)(this._sendInterval100NS - mod100NS)) / 10000);
                if (this._sendDataIntervalToken.WaitOne(ts))
                {
                    //资源已释放,取消发送
                    return true;
                }
            }

            return false;
        }
        #endregion


        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                base.Dispose(disposing);

                this.ReleaSeserialPort();

                var thread = this._asynOpenThread;
                if (thread != null)
                {
                    thread.Stop();
                    thread.Dispose();
                    this._asynOpenThread = null;
                }

                if (this._sendDataIntervalToken != null)
                {
                    this._sendDataIntervalToken.Dispose();
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        private void ReleaSeserialPort()
        {
            try
            {
                ISerialPort serialPort = this._serialPort;
                if (serialPort == null)
                {
                    return;
                }

                if (serialPort.IsOpen)
                {
                    serialPort.Close();
                }

                serialPort.DataReceived -= SerialPort_DataReceived;
                serialPort.ErrorReceived -= SerialPort_ErrorReceived;
                serialPort.PinChanged -= SerialPort_PinChanged;
                serialPort.Dispose();
                this._serialPort = null;
                this._dataLength = 0;
                this._bufferReadOffset = 0;
                this._bufferWirteOffset = 0;
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

    }
}
